
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JTextPane;

public class Process implements Runnable {

    boolean passo = false;
    JTextPane text;
    int tipo;
    String token, nome, mensagem;
    ServerComunication server;
    ArrayList<String> nomeDosProcessos;
    HashMap<String, ComunicationUniCast> comunicaçãoComOsProcessos;
    HashMap<String, String> mensagensComOsProcessos;
    HashMap<String, Integer> consenso;
    HashMap<String, ServerComunication> comunicaçãoComOsProcessosServidor;

    public Process(JTextPane text, String nome, int tipo, int porta, HashMap<String, ComunicationUniCast> aux, ArrayList<String> processos) {
        this.text = text;
        this.nome = nome;
        this.tipo = tipo;
        comunicaçãoComOsProcessos = aux;
        nomeDosProcessos = processos;
        mensagensComOsProcessos = new HashMap<String, String>();
        server = new ServerComunication(this.text, porta);

    }

    public void comecaServidor() {
        new Thread(server).start();
        espera();

    }

    public void comecaCliente() {
        ComunicationUniCast aux;
        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase(nome)) {
                aux = comunicaçãoComOsProcessos.get(s);
                new Thread(aux).start();
                espera();
                espera();
            }
        }
    }

    public void escreve(String s) {
        text.setText((!(text.getText().isEmpty()) ? text.getText() : "\n") + s + "\n");
    }

    public void passar() {
        passo = true;
    }

    public void run() {
        comecaServidor();
        passo = false;
        escreve("Esperando Click para se conectar aos outros processos e nodo.");
        while (!passo) {
            System.out.print("");
        }
        escreve("Clickei.");
        comecaCliente();
        passo = false;
        escreve("Esperando Click para começar.");
        while (!passo) {
            System.out.print("");
        }
        escreve("Comecei.");
        while (true) {
            escreve("------------------------------------------------------------------");
            // recebe o pacote do distributor
            espera();
            while (!recebeuDoDistribuidor()) {
                escreve("Ainda não recebi do distribuidor.");
                espera();
            }

            if (cordoToken().equalsIgnoreCase("vermelho")) {
                // simula processamento
                String aux = "Processando";
                for (int i = 1; i < 100000000; i++) {
                    if ((i % 10000000) == 0) {
                        aux = aux + ".";
                        escreve(aux);
                    }
                }
            }
            // calcula o token
            if (tipo == 0) {
                //Normal
                calculaToken();
            } else {
                //Bizantino
                calculaTokenBizantino();
            }
            // divulga o valor do token  // mando o token calculado para os outros processos

            //Passa para o distribuidor

            while (!enviaTodas()) {
                espera();
            }
            // recebe o token dos outros processos
            while (!recebeuTodas()) {
                espera();
            }
            // calcula o valor do token pela maioria
            consenso();
            // envia o token do consenso
            while (!enviaTodas()) {
                espera();
            }
            // recebe os tokens do consenso dos outros
            while (!recebeuTodas()) {
                espera();
            }
            // ver se está correto
            if (consensoAlcançado()) {
                // envia o token para o distributor
                escreve("Consenso alcançado. Mandando token para o Distribuidor.");
                espera();
                enviaParaDistribuidor(token);
            } else {
                escreve("Problema em alcançar o consenso.");
            }
            escreve("-------------------------------------------------------------------");
        }
    }

    private boolean recebeuDoDistribuidor() {
        //fica no aguardo pelo o pacote que vem do distribuidor
        escreve("Ainda não Recebi do Distribuidor.");
        //ComunicationUniCast aux = comunicaçãoComOsProcessos.get("nodo");
        try {
            token = server.recebe();
            escreve(token);
        } catch (IOException ex) {
            escreve("Problema em receber do Distribuidor.");
        }
        escreve("Recebi do Distribuidor.");
        if (token == null) {
            return false;
        } else {
            return true;
        }
    }

    private String cordoToken() {
        String[] split = token.split(":");
        return split[3];
    }

    private void calculaToken() {
        if (cordoToken().equalsIgnoreCase("vermelho")) {
            //<IDENTIFICADOR>:<TIPO DE PACOTE>:<DADOS>
            token = nome + ":TOKEN:0";
        } else if (cordoToken().equalsIgnoreCase("azul")) {
            //fase++
            String[] s = token.split(":");
            System.out.println(s[0]+"-"+s[1]+"-"+s[2]+"-");
            int auxF = Integer.parseInt(s[2]);
            token = nome + ":TOKEN:" + (auxF + 1);
        } else {
            escreve("Problema no calculo do Token");
        }
    }

    private void calculaTokenBizantino() {
        //O comportamento bizantino ele sempre colocar um valor a mais no token
        if (cordoToken().equalsIgnoreCase("vermelho")) {
            //<IDENTIFICADOR>:<TIPO DE PACOTE>:<DADOS>
            token = nome + ":TOKEN:1";
        } else if (cordoToken().equalsIgnoreCase("azul")) {
            //fase++
            int auxF = Integer.parseInt(token.split(":")[2]);
            token = nome + ":TOKEN:" + (auxF + 2);
        } else {
            escreve("Problema no calculo do Token");
        }
    }

    private boolean enviaTodas() {
        try {
            /*
             * ComunicationUniCast aux; escreve("Enviar o token para os outros
             * processos"); for (String s : nomeDosProcessos) { if
             * (!s.equalsIgnoreCase("nodo") && !s.equalsIgnoreCase(nome)) {
             * escreve("Enviando o token: " + token + " para: " + s); aux =
             * comunicaçãoComOsProcessos.get(s); if (aux == null) {
             * escreve("problema no envioTodas"); } aux.envia(token); espera();
             * }
            }
             */

            server.envia(token);
        } catch (IOException ex) {
            escreve("Problema no envia todos");
        }
        return true;
    }

    private boolean recebeuTodas() {

        /*
        escreve("Começar a receber os outros tokens.");
        ComunicationUniCast aux;
        String aux1;
        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase("nodo") && !s.equalsIgnoreCase(nome)) {

                aux = comunicaçãoComOsProcessos.get(s);
                escreve(s);
                if (aux == null) {
                    escreve("problema no recebeTodas");
                }
                espera();
                espera();
                aux1 = aux.recebe();
                escreve("Recebi a mensagem do " + s + ": " + aux1);
                mensagensComOsProcessos.put(s, aux1);

            }
        }*/
        recebeuDoDistribuidor();

        String[] split = token.split("_");
        for(String s : split){
            String[] split1 = s.split(":");
            mensagensComOsProcessos.put(split1[0],split1[1]+":"+split1[2]);
        }



        return true;
    }

    private void consenso() {

        consenso = new HashMap<String, Integer>();
        String aux;
        //contabiliza o proprio token
        consenso.put(token, 1);

        escreve("Começando consenso.");
        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase("nodo") && !s.equalsIgnoreCase(nome)) {
                Integer a = consenso.get(mensagensComOsProcessos.get(s));
                if (a == null) {
                    consenso.put(mensagensComOsProcessos.get(s), 1);
                } else {
                    consenso.put(mensagensComOsProcessos.get(s), a + 1);
                }
            }
        }

        Integer maior = consenso.get(token);
        String auxt = token;

        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase("nodo") && !s.equalsIgnoreCase(nome)) {
                Integer teste = consenso.get(mensagensComOsProcessos.get(s));
                if (teste > maior) {
                    maior = teste;
                    auxt = mensagensComOsProcessos.get(s);
                }
            }


        }

        escreve("O Token do consenso é: " + auxt);
        token = auxt;
    }

    private boolean consensoAlcançado() {

        String aux = nomeDosProcessos.get(1);
        for (String s : nomeDosProcessos) {
            if (!s.equalsIgnoreCase("nodo") && !s.equalsIgnoreCase(nome)) {
                if (!(mensagensComOsProcessos.get(aux).equalsIgnoreCase(mensagensComOsProcessos.get(s)))) {
                    return false;
                }
            }
        }
        token = mensagensComOsProcessos.get(aux);
        return true;
    }

    private void enviaParaDistribuidor(String s) {
        ComunicationUniCast aux;
        aux = comunicaçãoComOsProcessos.get("nodo");
        try {
            server.envia(s);
        } catch (IOException ex) {
            escreve("Problema ao mandar para o distribuidor");
        }
        espera();
    }

    private void espera() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException ex) {
            escreve("Problema ao esperar.");
        }
    }
}